import * as fs from 'fs';
import ccxt from '../../js/ccxt.js';

// Fetch balances for all exchanges, all accounts, asynchronously and output a consolidated balance object

interface ExchangeConfig {
    apiKey: string;
    secret: string;
    options?: {
        owner?: string;
        [key: string]: any;
    };
    [key: string]: any;
}

interface KeysConfig {
    [exchangeId: string]: ExchangeConfig;
}

interface Balance {
    [exchangeId: string]: {
        [marketType: string]: {
            [currency: string]: string;
        };
    };
}

interface MarketBalances {
    [marketType: string]: {
        [currency: string]: string
    }
}

/**
 * @description Helper function to compare two balance objects
 * @param bal1
 * @param bal2
 * @returns {boolean} true if balances are equal
 */
function balancesEqual (bal1: { [currency: string]: string }, bal2: { [currency: string]: string }): boolean {
    const keys1 = Object.keys (bal1).sort ();
    const keys2 = Object.keys (bal2).sort ();
    if (JSON.stringify (keys1) !== JSON.stringify (keys2)) {
        return false;
    }
    for (let i = 0; i < keys1.length; i++) {
        const key = keys1[i];
        if (bal1[key] !== bal2[key]) {
            return false;
        }
    }
    return true;
}

async function fetchAllBalances () {
    try {
        const keysData = fs.readFileSync ('keys.local.json', 'utf-8');
        const exchanges: [string, KeysConfig][] = Object.entries (JSON.parse (keysData));

        const marketTypes = [
            { 'type': 'spot', 'params': {}},
            { 'type': 'margin', 'params': { 'type': 'margin' }},
            { 'type': 'swap', 'params': { 'type': 'swap' }},
            { 'type': 'swap', 'params': { 'type': 'swap', 'subType': 'linear' }},
            { 'type': 'swap', 'params': { 'type': 'swap', 'subType': 'inverse' }},
            { 'type': 'derivatives', 'params': { 'type': 'derivatives' }},
            { 'type': 'funding', 'params': { 'type': 'funding' }},
            { 'type': 'main', 'params': { 'type': 'main' }},
            { 'type': 'cross', 'params': { 'marginMode': 'cross' }},
            { 'type': 'isolated', 'params': { 'marginMode': 'isolated' }},
            { 'type': 'trading', 'params': { 'marginMode': 'trading' }},
            { 'type': 'cash', 'params': { 'type': 'cash' }},
            { 'type': 'future', 'params': { 'type': 'future' }},
            { 'type': 'account', 'params': { 'type': 'account' }},
            { 'type': 'financial', 'params': { 'type': 'financial' }},
            { 'type': 'wallet', 'params': { 'type': 'wallet' }},
        ];

        // create exchange instances and fetch balances in parallel
        const balancePromises = exchanges.map (async ([ exchangeId, config ]) => {
            try {
                const CCXT = ccxt as any;
                const ExchangeClass = CCXT[exchangeId];

                if (!ExchangeClass) {
                    throw new Error (`Exchange ${exchangeId} not found in CCXT`);
                }

                const exchange = new ExchangeClass ({
                    'apiKey': config.apiKey,
                    'secret': config.secret,
                    'options': config.options,
                    'enableRateLimit': true,
                    'timeout': 30000,
                });

                // fetch balances for all market types
                const balanceResults = await Promise.allSettled (
                    marketTypes.map (async ({ type, params }) => {
                        try {
                            const balance = await exchange.fetchBalance (params);
                            return {
                                'marketType': type,
                                'success': true,
                                'balance': balance,
                                'error': null,
                            };
                        } catch (error) {
                            return {
                                'marketType': type,
                                'success': false,
                                'balance': null,
                                'error': error instanceof Error ? error.message : String (error),
                            };
                        }
                    })
                );

                const balances = balanceResults.map ((result) => (result.status === 'fulfilled' ? result.value : {
                    'marketType': 'unknown',
                    'success': false,
                    'balance': null,
                    'error': 'Promise rejected',
                }));

                return {
                    'exchangeId': exchangeId,
                    'success': true,
                    'balances': balances,
                    'error': null,
                };
            } catch (error) {
                return {
                    'exchangeId': exchangeId,
                    'success': false,
                    'balances': null,
                    'error': error instanceof Error ? error.message : String (error),
                };
            }
        });

        const results = await Promise.all (balancePromises);

        const balanceObject: Balance = {};

        results.forEach (({ exchangeId, success, balances }) => {
            if (success && balances) {
                // collect all balances for each market type
                const marketBalances: MarketBalances = {};

                balances.forEach (({ marketType, 'success': balanceSuccess, balance }) => {
                    if (balanceSuccess && balance) {
                        marketBalances[marketType] = {};

                        Object.entries (balance).forEach (([ currency, info ]: [string, any]) => {
                            if (typeof info === 'object' && info !== null && parseFloat (info.total || '0') > 0) {
                                marketBalances[marketType][currency] = info.total;
                            }
                        });
                    }
                });

                // Filter out empty market types (no balances > 0)
                const nonEmptyMarketTypes = Object.keys (marketBalances).filter ((marketType) => {
                    const balances = marketBalances[marketType];
                    return balances && Object.keys (balances).length > 0;
                });

                // Find unique market types (remove duplicates)
                const uniqueMarketTypes: string[] = [];
                const seenBalances: { [key: string]: string }[] = [];

                nonEmptyMarketTypes.forEach ((marketType) => {
                    const balances = marketBalances[marketType];
                    const isDuplicate = seenBalances.some ((seenBalance) => balancesEqual (seenBalance, balances));

                    if (!isDuplicate) {
                        uniqueMarketTypes.push (marketType);
                        seenBalances.push (balances);
                    }
                });

                balanceObject[exchangeId] = {};
                uniqueMarketTypes.forEach ((marketType) => {
                    balanceObject[exchangeId][marketType] = marketBalances[marketType];
                });
            }
        });

        console.log (JSON.stringify (balanceObject, null, 2));
    } catch (error) {
        console.error ('Error reading keys file:', error);
    }
}

try {
    fetchAllBalances ();
} catch (error) {
    console.error (error);
}
